//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <bsp.h>
#include <mmu.h>
#include <multiboot.h>

const uint_t c_uMaxNumberOfInitStackPages   = 0x1000;   // 16M
const uint_t c_uMaxNumberOfMemoryZones      = 3;
const uint_t c_uMaxNumberOfMemoryHoles      = 8;

EXTERN_C_BEGIN

STATIC DECL_DATAINIT MemoryZone    s_memoryZones[c_uMaxNumberOfMemoryZones];
STATIC DECL_DATAINIT MemoryHole    s_memoryHoles[c_uMaxNumberOfMemoryHoles];

EXTERN_C_END

const uint_t c_uMaxNumberOfRams     = 20;

STATIC DECL_DATAINIT MemoryRegion   s_rams[c_uMaxNumberOfRams];
STATIC DECL_DATAINIT uint_t         s_uNumberOfRams = 0;

STATIC DECL_CODEINIT void GetInitStack();
STATIC DECL_CODEINIT void GetMemoryZones(multiboot_info_t *pMultibootInfo);
STATIC DECL_CODEINIT void GetMemoryHoles(multiboot_info_t *pMultibootInfo);
STATIC DECL_CODEINIT void RearrangeRams();
STATIC DECL_CODEINIT void MergeRams();
STATIC DECL_CODEINIT void BuildMemoryHoles();

#define  MEMORY_LIMIT_32M     0x7C00

EXTERN_C DECL_CODEINIT void CDECL InitMemoryZones(
    multiboot_info_t *pMultibootInfo)
{
    multiboot_info_t    *pBootInfo = \
                    (multiboot_info_t *)VIRTADDR((physaddr_t)pMultibootInfo);
    if (pBootInfo->mem_upper > MEMORY_LIMIT_32M)
        pBootInfo->mem_upper = MEMORY_LIMIT_32M ;

    GetInitStack();

    GetMemoryZones((multiboot_info_t *)VIRTADDR((physaddr_t)pMultibootInfo));
    GetMemoryHoles((multiboot_info_t *)VIRTADDR((physaddr_t)pMultibootInfo));
}

STATIC DECL_CODEINIT void GetInitStack()
{
    char c;

    bsp.regionOfInitStack.uLimitPageNo =
        VIRTADDR_TO_PAGENO(RoundUp2((offset_t)&c, PAGE_SIZE));

    bsp.regionOfInitStack.uBasePageNo =
        (c_uMaxNumberOfInitStackPages < bsp.regionOfInitStack.uLimitPageNo)?
        bsp.regionOfInitStack.uLimitPageNo - c_uMaxNumberOfInitStackPages : 0;
}

const uint_t c_uNumberOfDmaPages = 0x1000;   // 16M

#define KILO    0x400

STATIC DECL_CODEINIT void GetMemoryZones(multiboot_info_t *pMultibootInfo)
{
    uint_t uPagesOfMemory =
        NUMBEROF_PAGES((pMultibootInfo->mem_upper + KILO) * KILO);

    bsp.pMemoryZones = s_memoryZones;

    if (uPagesOfMemory <= c_uNumberOfDmaPages) {
        bsp.uNumberOfMemoryZones          = 1;
        s_memoryZones[0].uBasePageNo    = 0;
        s_memoryZones[0].uLimitPageNo   = uPagesOfMemory;

        bsp.uKernelPreferredZoneNo        = 0;
        bsp.uUserPreferredZoneNo          = 0;
    }
    else if (uPagesOfMemory <= c_uKernelBasePageNo) {
        bsp.uNumberOfMemoryZones          = 2;
        s_memoryZones[0].uBasePageNo    = 0;
        s_memoryZones[0].uLimitPageNo   = c_uNumberOfDmaPages;

        s_memoryZones[1].uBasePageNo    = c_uNumberOfDmaPages;

        if (uPagesOfMemory <= c_uNumberOfKernelPages) {
            s_memoryZones[1].uLimitPageNo   = uPagesOfMemory;
        }
        else {
            s_memoryZones[1].uLimitPageNo   = c_uNumberOfKernelPages;

            bsp.uNumberOfMemoryZones++;
            s_memoryZones[2].uBasePageNo    = c_uNumberOfKernelPages;
            s_memoryZones[2].uLimitPageNo   = uPagesOfMemory;
        }

        bsp.uKernelPreferredZoneNo        = 1;
        bsp.uUserPreferredZoneNo          = 1;
    }
    else {
        bsp.uNumberOfMemoryZones          = 3;
        s_memoryZones[0].uBasePageNo    = 0;
        s_memoryZones[0].uLimitPageNo   = c_uNumberOfDmaPages;

        s_memoryZones[1].uBasePageNo    = c_uNumberOfDmaPages;
        s_memoryZones[1].uLimitPageNo   = c_uNumberOfKernelPages;

        s_memoryZones[2].uBasePageNo    = c_uNumberOfKernelPages;
        s_memoryZones[2].uLimitPageNo   = uPagesOfMemory;

        bsp.uKernelPreferredZoneNo        = 1;
        bsp.uUserPreferredZoneNo          = 1;
    }
}

// Multiboot Specification 3.3 Boot information format
// If bit 6 in the `flags' word is set, then the `mmap_*' fields are valid,
// and indicate the address and length of a buffer containing a memory map
// of the machine provided by the BIOS. `mmap_addr' is the address,
// and `mmap_length' is the total size of the buffer. The buffer consists of
// one or more of the following size/structure pairs (`size' is really used
// for skipping to the next pair):
//         +-------------------+
// -4      | size              |
//         +-------------------+
// 0       | base_addr_low     |
// 4       | base_addr_high    |
// 8       | length_low        |
// 12      | length_high       |
// 16      | type              |
//         +-------------------+

#define MMAP_AVAILABLE      __8BIT(6)
#define RAM_AVAILABLE       1

STATIC DECL_CODEINIT void GetMemoryHoles(multiboot_info_t *pMultibootInfo)
{
    uint_t uPagesOfMemory =
        NUMBEROF_PAGES((pMultibootInfo->mem_upper + KILO) * KILO);

    memory_map_t    memoryMaps[2];
    memory_map_t    *pMemoryMaps;
    uint_t          uMemoryMapLength;

    if (pMultibootInfo->flags & MMAP_AVAILABLE) {
        pMemoryMaps      = (memory_map_t *)VIRTADDR(pMultibootInfo->mmap_addr);
        uMemoryMapLength = pMultibootInfo->mmap_length;
    }
    else {
        memoryMaps[0].size           = sizeof(memory_map_t)
            - sizeof(memoryMaps[0].size);
        memoryMaps[0].base_addr_low  = 0;
        memoryMaps[0].base_addr_high = 0;
        memoryMaps[0].length_low     = pMultibootInfo->mem_lower * KILO;
        memoryMaps[0].length_high    = 0;
        memoryMaps[0].type           = RAM_AVAILABLE;

        memoryMaps[1].size           = sizeof(memory_map_t)
            - sizeof(memoryMaps[1].size);
        memoryMaps[1].base_addr_low  = KILO * KILO;
        memoryMaps[1].base_addr_high = 0;
        memoryMaps[1].length_low     = pMultibootInfo->mem_upper * KILO;
        memoryMaps[1].length_high    = 0;
        memoryMaps[1].type           = RAM_AVAILABLE;

        pMemoryMaps                  = memoryMaps;
        uMemoryMapLength             = sizeof(memoryMaps);
    }

    for (memory_map_t *pMemoryMap = pMemoryMaps;
        (byte_t *)pMemoryMap < (byte_t *)pMemoryMaps + uMemoryMapLength;
        pMemoryMap = (memory_map_t *)((byte_t *)pMemoryMap
        + pMemoryMap->size + sizeof(pMemoryMap->size))) {
        uint_t uBasePageNo  = PHYSADDR_TO_PAGENO(pMemoryMap->base_addr_low);
        uint_t uLimitPageNo = uBasePageNo
            + NUMBEROF_PAGES(pMemoryMap->length_low);

        if (RAM_AVAILABLE == pMemoryMap->type) {
            s_rams[s_uNumberOfRams].uBasePageNo     = uBasePageNo;
            s_rams[s_uNumberOfRams].uLimitPageNo    =
                Min(uLimitPageNo, uPagesOfMemory);
            s_uNumberOfRams++;

            // must reserve two ram area for init stack and boot modules
            if (s_uNumberOfRams >= c_uMaxNumberOfRams - 2) break;
        }
    }

    RearrangeRams();
    MergeRams();

    BuildMemoryHoles();
}

STATIC DECL_CODEINIT void RearrangeRams()
{
    uint_t i;

    // Add the ram of init stack
    uint_t uRamNo = 0;
    while (uRamNo < s_uNumberOfRams
        && s_rams[uRamNo].uBasePageNo < bsp.regionOfInitStack.uBasePageNo) {
        uRamNo++;
    }

    for (i = s_uNumberOfRams; i > uRamNo; i--) {
        s_rams[i] = s_rams[i - 1];
    }
    s_rams[uRamNo] = bsp.regionOfInitStack;
    s_uNumberOfRams++;

    // Add the ram of boot modules
    uRamNo = 0;
    while (uRamNo < s_uNumberOfRams
        && s_rams[uRamNo].uBasePageNo < bsp.regionOfBootModules.uBasePageNo) {
        uRamNo++;
    }

    for (i = s_uNumberOfRams; i > uRamNo; i--) {
        s_rams[i] = s_rams[i - 1];
    }
    s_rams[uRamNo] = bsp.regionOfBootModules;
    s_uNumberOfRams++;
}

STATIC DECL_CODEINIT void MergeRams()
{
    MemoryRegion mergedRams[c_uMaxNumberOfRams];

    uint_t i = 0;
    uint_t j = 0;
    while (i < s_uNumberOfRams) {
        if (s_rams[i].uLimitPageNo < s_rams[i + 1].uBasePageNo) {
            mergedRams[j] = s_rams[i];
            i++;
            j++;
        }
        else {
            s_rams[i + 1].uBasePageNo   = s_rams[i].uBasePageNo;
            s_rams[i + 1].uLimitPageNo  =
                Max(s_rams[i].uLimitPageNo, s_rams[i + 1].uLimitPageNo);
            i++;
        }
    }
    mergedRams[j] = s_rams[i];

    s_uNumberOfRams = j + 1;
    for (i = 0; i < s_uNumberOfRams; i++) {
        s_rams[i] = mergedRams[i];
    }
}

STATIC DECL_CODEINIT void BuildMemoryHoles()
{
    bsp.pMemoryHoles = s_memoryHoles;
    bsp.uNumberOfMemoryHoles = 0;
    if (0 != s_rams[0].uBasePageNo) {
        s_memoryHoles[bsp.uNumberOfMemoryHoles].uBasePageNo   = 0;
        s_memoryHoles[bsp.uNumberOfMemoryHoles].uLimitPageNo  =
            s_rams[0].uBasePageNo;
        bsp.uNumberOfMemoryHoles++;
    }

    for (uint_t i = 0; i < s_uNumberOfRams - 1; i++) {
        s_memoryHoles[bsp.uNumberOfMemoryHoles].uBasePageNo   =
            s_rams[i].uLimitPageNo;
        s_memoryHoles[bsp.uNumberOfMemoryHoles].uLimitPageNo  =
            s_rams[i + 1].uBasePageNo;
        bsp.uNumberOfMemoryHoles++;
    }

    if (s_rams[s_uNumberOfRams - 1].uLimitPageNo < c_uMaxNumberOfPages) {
        s_memoryHoles[bsp.uNumberOfMemoryHoles].uBasePageNo   =
            s_rams[s_uNumberOfRams - 1].uLimitPageNo;
        s_memoryHoles[bsp.uNumberOfMemoryHoles].uLimitPageNo  =
            c_uMaxNumberOfPages;
        bsp.uNumberOfMemoryHoles++;
    }
}
